package com.ozo.cloud.common.log.aspect;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;

import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.ozo.cloud.common.log.annotation.OperateLog;
import com.ozo.cloud.common.log.properties.LogProperties;
import com.ozo.cloud.api.system.dto.SysOperationLogDTO;
import com.ozo.cloud.common.log.event.OperationLogEvent;
import com.ozo.cloud.common.satoken.context.UserLoginHelper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
import net.dreamlu.mica.ip2region.core.IpInfo;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * 操作日志记录处理
 *
 * @author qiangesoft
 * @date 2024-04-02
 */
@Slf4j
@Aspect
public class OperateLogAspect {

    /**
     * 环绕通知
     *
     * @param joinPoint
     * @param operateLog
     */
    @SneakyThrows
    @Around(value = "@annotation(operateLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable {
        // 日志未开启，直接执行业务
        LogProperties logProperties = SpringUtil.getBean(LogProperties.class);
        if (!logProperties.isEnabled()) {
            return joinPoint.proceed();
        }

        // 日志信息
        long startTime = System.currentTimeMillis();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();

        SysOperationLogDTO operationLog = new SysOperationLogDTO();
        operationLog.setOperationUser(UserLoginHelper.getUsername());
        operationLog.setOperationTime(LocalDateTime.now());
        // 业务信息
        operationLog.setModule(operateLog.module());
        operationLog.setOperationName(operateLog.value());
        operationLog.setMethod(className + "." + methodName + "()");
        // 请求信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        operationLog.setRequestUrl(request.getRequestURI());
        operationLog.setRequestMethod(request.getMethod());
        // 位置信息
        String ipAddr = JakartaServletUtil.getClientIP(request);
        operationLog.setRequestIp(ipAddr);
        IpInfo ipInfo = SpringUtil.getBean(Ip2regionSearcher.class).memorySearch(ipAddr);
        if (ipInfo != null) {
            operationLog.setRequestLocation(StringUtils.isNotBlank(ipInfo.getCountry()) ? ipInfo.getCountry() + ipInfo.getProvince() + ipInfo.getCity() + ipInfo.getIsp() : ipInfo.getIsp());
        }
        if (logProperties.isSaveParam()) {
            // 请求参数
            if (request.getParameterMap() != null && !request.getParameterMap().isEmpty()) {
                Map<String, String[]> requestParam = new HashMap<>(request.getParameterMap());
                Map<String, String[]> paramsMap = MapUtil.removeAny(requestParam, ArrayUtil.toArray(logProperties.getExcludeFields(), String.class));
                operationLog.setRequestParam(HttpUtil.toParams(paramsMap));
            }
            // 请求体
            operationLog.setRequestBody(JSONUtil.toJsonStr(joinPoint.getArgs()));
        }

        try {
            Object obj = joinPoint.proceed();
            operationLog.setSuccessFlag(true);
            // 响应数据
            if (logProperties.isSaveReturn()) {
                operationLog.setResponseData(obj == null ? null : JSONUtil.toJsonStr(obj));
            }
            return obj;
        } catch (Exception e) {
            operationLog.setSuccessFlag(false);
            operationLog.setErrorMsg(e.getMessage());
            throw e;
        } finally {
            operationLog.setCostTime(System.currentTimeMillis() - startTime);
            SpringUtil.publishEvent(new OperationLogEvent(operationLog));
        }
    }

}
